#include <iostream>
#include <sstream>
#include <ruby.h>
#include "m3g/m3g.hpp"
#include "ruby-m3g.hpp"
using namespace m3g;
using namespace std;


namespace {
    struct NormalAccessor {
        RayIntersection* ray_intersection;
    };
    struct TextureAccessor {
        RayIntersection* ray_intersection;
    };
    struct TextureSTAccessor {
        RayIntersection* ray_intersection;
        int              index;
    };
    VALUE rb_cRayIntersection_NormalAccessor;
    VALUE rb_cRayIntersection_TextureAccessor;
    VALUE rb_cRayIntersection_TextureSTAccessor;
}

void ruby_RayIntersection_mark (RayIntersection* ptr)
{
    rb_gc_mark ((VALUE)ptr->getExportedEntity());
}

void ruby_RayIntersection_free (RayIntersection* ptr)
{
    if (valid_ptr((unsigned char*)ptr, sizeof(*ptr))) {
        __TRY__;
        ptr->~RayIntersection ();
        __CATCH__;
    }
    ruby_xfree (ptr);
}

VALUE ruby_RayIntersection_allocate (VALUE self)
{
    void* p = ruby_xmalloc (sizeof(RayIntersection));
    return Data_Wrap_Struct (self, ruby_RayIntersection_mark, ruby_RayIntersection_free, p);
}

VALUE ruby_RayIntersection_initialize (VALUE self)
{
    RayIntersection* p;
    Data_Get_Struct (self, RayIntersection, p);
    __TRY__;
    new (p) RayIntersection;
    __CATCH__;
    p->setExportedEntity((void*)self);
    return self;
}

VALUE ruby_RayIntersection_get_distance (VALUE self)
{
    RayIntersection* p;
    Data_Get_Struct (self, RayIntersection, p);
    float distance;
    __TRY__;
    distance = p->getDistance ();
    __CATCH__;
    return rb_float_new(distance);
}

VALUE ruby_RayIntersection_get_intersected (VALUE self)
{
    RayIntersection* p;
    Data_Get_Struct (self, RayIntersection, p);
    Node* node;
    __TRY__;
    node = p->getIntersected ();
    __CATCH__;
    return node ? (VALUE)node->getExportedEntity() : Qnil;
}

VALUE ruby_RayIntersection_get_normal (VALUE self)
{
    RayIntersection* p;
    Data_Get_Struct (self, RayIntersection, p);
    NormalAccessor* accessor;
    VALUE       val_accessor = Data_Make_Struct (rb_cRayIntersection_NormalAccessor, NormalAccessor, 0, -1, accessor);
    accessor->ray_intersection = p;
    return val_accessor;
}


VALUE ruby_RayIntersection_get_ray (VALUE self)
{
    RayIntersection* p;
    Data_Get_Struct (self, RayIntersection, p);
    float ray[6];
    __TRY__;
    p->getRay (ray);
    __CATCH__;
    VALUE val_ray = rb_ary_new2 (6);
    rb_ary_push (val_ray, rb_float_new(ray[0]));
    rb_ary_push (val_ray, rb_float_new(ray[1]));
    rb_ary_push (val_ray, rb_float_new(ray[2]));
    rb_ary_push (val_ray, rb_float_new(ray[3]));
    rb_ary_push (val_ray, rb_float_new(ray[4]));
    rb_ary_push (val_ray, rb_float_new(ray[5]));
    return val_ray;
}

VALUE ruby_RayIntersection_get_submesh_index (VALUE self)
{
    RayIntersection* p;
    Data_Get_Struct (self, RayIntersection, p);
    int index;
    __TRY__;
    index = p->getSubmeshIndex();
    __CATCH__;
    return INT2NUM(index);
}

VALUE ruby_RayIntersection_get_texture (VALUE self)
{
    RayIntersection* p;
    Data_Get_Struct (self, RayIntersection, p);
    TextureAccessor* accessor;
    VALUE        val_accessor = Data_Make_Struct (rb_cRayIntersection_TextureAccessor, TextureAccessor, 0, -1, accessor);
    accessor->ray_intersection = p;
    return val_accessor;
}

VALUE ruby_RayIntersection_print (VALUE self)
{
    RayIntersection* p;
    Data_Get_Struct (self, RayIntersection, p);
    ostringstream out;
    __TRY__;
    p->print (out);
    __CATCH__;
    return rb_str_new2 (out.str().c_str());
}



/**
 * RayIntersection::TextureAccessor
 */
VALUE ruby_RayIntersection_TextureAccessor_get_st (VALUE self, VALUE val_index)
{
    TextureAccessor* p;
    Data_Get_Struct (self, TextureAccessor, p);
    int index = NUM2INT(val_index);
    TextureSTAccessor* accessor;
    VALUE          val_accessor = Data_Make_Struct (rb_cRayIntersection_TextureSTAccessor, TextureSTAccessor, 0, -1, accessor);
    accessor->ray_intersection = p->ray_intersection;
    accessor->index = index;
    return val_accessor;
}


/**
 * RayIntersection::TextureSTAccessor
 */
VALUE ruby_RayIntersection_TextureSTAccessor_get_s (VALUE self)
{
    TextureSTAccessor* p;
    Data_Get_Struct (self, TextureSTAccessor, p);
    float s;
    __TRY__;
    s = p->ray_intersection->getTextureS (p->index);
    __CATCH__;
    return rb_float_new(s);
}

VALUE ruby_RayIntersection_TextureSTAccessor_get_t (VALUE self)
{
    TextureSTAccessor* p;
    Data_Get_Struct (self, TextureSTAccessor, p);
    float t;
    __TRY__;
    t = p->ray_intersection->getTextureT (p->index);
    __CATCH__;
    return rb_float_new(t);
}


/**
 * RayIntersection:: NormalAccessor
 */
VALUE ruby_RayIntersection_NormalAccessor_get_x (VALUE self)
{
    NormalAccessor* p;
    Data_Get_Struct (self, NormalAccessor, p);
    float x;
    __TRY__;
    x = p->ray_intersection->getNormalX ();
    __CATCH__;
    return rb_float_new(x);
}

VALUE ruby_RayIntersection_NormalAccessor_get_y (VALUE self)
{
    NormalAccessor* p;
    Data_Get_Struct (self, NormalAccessor, p);
    float y;
    __TRY__;
    y = p->ray_intersection->getNormalY ();
    __CATCH__;
    return rb_float_new(y);
}

VALUE ruby_RayIntersection_NormalAccessor_get_z (VALUE self)
{
    NormalAccessor* p;
    Data_Get_Struct (self, NormalAccessor, p);
    float z;
    __TRY__;
    z = p->ray_intersection->getNormalZ ();
    __CATCH__;
    return rb_float_new(z);
}



/**
 *
 */
void register_RayIntersection ()
{
    // RayIntersection
    rb_cRayIntersection     = rb_define_class_under (rb_mM3G, "RayIntersection",     rb_cObject);

    rb_define_alloc_func (rb_cRayIntersection, ruby_RayIntersection_allocate);
    rb_define_private_method (rb_cRayIntersection, "initialize", (VALUE(*)(...))ruby_RayIntersection_initialize,   0);

    rb_define_method (rb_cRayIntersection, "distance",      (VALUE(*)(...))ruby_RayIntersection_get_distance,      0);
    rb_define_method (rb_cRayIntersection, "intersected",   (VALUE(*)(...))ruby_RayIntersection_get_intersected,   0);
    rb_define_method (rb_cRayIntersection, "normal",        (VALUE(*)(...))ruby_RayIntersection_get_normal,        0);
    rb_define_method (rb_cRayIntersection, "ray",           (VALUE(*)(...))ruby_RayIntersection_get_ray,           0);
    rb_define_method (rb_cRayIntersection, "submesh_index", (VALUE(*)(...))ruby_RayIntersection_get_submesh_index, 0);
    rb_define_method (rb_cRayIntersection, "texture",       (VALUE(*)(...))ruby_RayIntersection_get_texture,       0);
    rb_define_method (rb_cRayIntersection, "to_s",          (VALUE(*)(...))ruby_RayIntersection_print,             0);


    // RayIntersection:: NormalAccessor
    rb_cRayIntersection_NormalAccessor  = rb_define_class_under (rb_cRayIntersection, "NormalAccessor", rb_cObject);

    rb_define_method (rb_cRayIntersection_NormalAccessor, "x",        (VALUE(*)(...))ruby_RayIntersection_NormalAccessor_get_x,    0);
    rb_define_method (rb_cRayIntersection_NormalAccessor, "y",        (VALUE(*)(...))ruby_RayIntersection_NormalAccessor_get_y,    0);
    rb_define_method (rb_cRayIntersection_NormalAccessor, "z",        (VALUE(*)(...))ruby_RayIntersection_NormalAccessor_get_z,    0);

    // RayIntersection::TextureAccessor
    rb_cRayIntersection_TextureAccessor  = rb_define_class_under (rb_cRayIntersection, "TextureAccessor", rb_cObject);

    rb_define_method (rb_cRayIntersection_TextureAccessor, "[]",        (VALUE(*)(...))ruby_RayIntersection_TextureAccessor_get_st,    1);

    // RayIntersection::TextureSTAccessor
    rb_cRayIntersection_TextureSTAccessor  = rb_define_class_under (rb_cRayIntersection, "TextureAccessor", rb_cObject);

    rb_define_method (rb_cRayIntersection_TextureSTAccessor, "s",        (VALUE(*)(...))ruby_RayIntersection_TextureSTAccessor_get_s,    0);
    rb_define_method (rb_cRayIntersection_TextureSTAccessor, "t",        (VALUE(*)(...))ruby_RayIntersection_TextureSTAccessor_get_t,    0);

}
